/****************************************************************
 * Filename: led_drv.c
 * -------------------------------
 * Copyright(C),
 * Author: zhenquan.qiu
 * Version: V1.0.0
 * Last modified: 22/12 2016 14:46
 * Description:
 *
 * Change Log:
 * NO.	Author		    Date		Modified
 * 00	zhenquan.qiu	22/12 2016
 ****************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static unsigned int led_major=0;

volatile unsigned long *GPHcon_base = NULL;
volatile unsigned long *GPHcon = NULL;
volatile unsigned long *GPHdat = NULL;

struct led_chr_dev {
    struct class    *pled_class;    //-- 表示一类设备，存储某些信息
    struct device   *pled_device;   //-- 表示一个设备
    struct cdev     *pled_cdev;

    unsigned int val;
};
struct led_chr_dev *pr16_led_dev = NULL;

static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *opps)
{
    printk(KERN_INFO "led read...\n");

    return 0;
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *opps)
{
    int ret;

    printk(KERN_INFO "led write...\n");

    /* 拷贝成功，返回0；拷贝失败，返回没有被拷贝的字节数 */
    ret = copy_from_user(&pr16_led_dev->val, buf, count);
    if (ret > 0) {
        printk(KERN_ERR "copy from user faild \n");
        return ret;
    }

    printk(KERN_INFO "val: %d, size: %d \n", pr16_led_dev->val, sizeof(volatile unsigned long *));

    if (pr16_led_dev->val)
        *GPHdat &= ~(0x1 << 16);
    else
        *GPHdat |=  (0x1 << 16);

    return ret?0:count;
}

static int led_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led open...\n");

    /*PE16 PE17*/
    /* 将物理地址映射为虚拟地址 */
    GPHcon_base = ioremap(0x01c20800 + 0x24 * 4, 0x20);
    GPHcon = GPHcon_base + 2;     //0x8
    GPHdat = GPHcon_base + 4;     //0x10

    *GPHcon &= ~(0x7 << 4) | ~(0x7 << 0);
    *GPHcon |=  (0x1 << 4) |  (0x1 << 0);

    *GPHdat = (0x1 << 16) | (0x1 << 17);

    return 0;
}

static int led_close(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led close...\n");

    iounmap(GPHcon_base);

    return 0;
}

/* 硬件操作方法 */
struct file_operations led_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .release = led_close,
    .write   = led_write,
    .read    = led_read,
};

static void setup_led_cdev(void)
{
    /* 为cdev结构体分配空间 */
    pr16_led_dev->pled_cdev = cdev_alloc();

    /* 初始化cdev结构体 */
    cdev_init(pr16_led_dev->pled_cdev, &led_fops);

    /* 注册cdev，加载到内核哈希表中 */
    cdev_add(pr16_led_dev->pled_cdev, MKDEV(led_major, 0), 1);
}

static int __init led_chr_init(void)
{
    int ret;
    dev_t devno;

    if (led_major) {
        /* 静态申请 */
        devno = MKDEV(led_major, 0);
        register_chrdev_region(devno, 1, "led_chr");    //-- 编号相关联的设备名称 (/proc/devices)
    }
    else {
        /* 动态申请 */
        alloc_chrdev_region(&devno, 0, 1, "led_chr");
        led_major = MAJOR(devno);
    }

    /* 分配本地结构体
     * param1: 要分配的大小
     * param2: 标号: GFP_KERNEL,表示如果分配不成功，则休眠
     */
    pr16_led_dev = kmalloc(sizeof(struct led_chr_dev), GFP_KERNEL);
    if (!pr16_led_dev) {
        printk(KERN_ERR "no memory for malloc! \n");
        ret = -ENOMEM;
        goto fail_kmalloc;
    }

    /* 构建cdev结构体 */
    setup_led_cdev();

    /* 穿件设备类 */
    pr16_led_dev->pled_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(pr16_led_dev->pled_class)) {
        printk(KERN_ERR "create led_class faild \n");
        ret = -EINVAL;
        goto fail_class_create;
    }

    /* 创建设备文件
     * param1: 设备所属class
     * param2: 父类， 一般为NULL
     * param3: dev_t 表示一个设备号，是一个无符号32位整形，其中高12位为主设备号，低20位为此设备号
     * param4:
     * param5:
     */
    pr16_led_dev->pled_device = device_create(pr16_led_dev->pled_class, NULL, MKDEV(led_major, 0), NULL, "led_dev");
    if (IS_ERR(pr16_led_dev->pled_device)) {
        printk(KERN_ERR "failed create led_device \n");
        ret = -ENODEV;
        goto fail_device_create;
    }

    return 0;

fail_device_create:
    class_destroy(pr16_led_dev->pled_class);

fail_class_create:
    cdev_del(pr16_led_dev->pled_cdev);
    kfree(pr16_led_dev);

fail_kmalloc:
    unregister_chrdev_region(MKDEV(led_major, 0), 1);

    return ret;
}

static void __exit led_chr_exit(void)
{
    device_destroy(pr16_led_dev->pled_class, MKDEV(led_major, 0));
    class_destroy(pr16_led_dev->pled_class);
    cdev_del(pr16_led_dev->pled_cdev);
    kfree(pr16_led_dev);
    unregister_chrdev_region(MKDEV(led_major, 0), 1);
}

module_init(led_chr_init);
module_exit(led_chr_exit);

MODULE_DESCRIPTION("led character device");
MODULE_AUTHOR("zhenquan.qiu <gnsyxiang@163.com>");
MODULE_LICENSE("GPL");

